use core::fmt;

use axplat_aarch64_opi5p::ethernet::{
    EthernetController, GmacId, GmacLinkSpeed, GmacPhyInterface, ethernet_controller,
};
use ethernet_rk3588::Delay as GmacDelay;

const SYS_GRF_GMAC_CON7: usize = 0x031c;
const SYS_GRF_GMAC_CON8: usize = 0x0320;
const SYS_GRF_GMAC_CON9: usize = 0x0324;

const PHP_GRF_GMAC_CON0: usize = 0x0008;
const PHP_GRF_CLK_CON1: usize = 0x0070;

/// Usage strings shared by Ethernet command front-ends.
pub const USAGE_LINES: &[&str] = &[
    "Usage: ethernet <command> [args]",
    "  ethernet init",
    "  ethernet status",
    "  ethernet speed <gmac0|gmac1> <10|100|1000>",
    "  ethernet delay <gmac0|gmac1> <tx-hex> <rx-hex>",
    "  ethernet help",
];

/// Ethernet subcommand enumeration.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Command {
    /// Re-apply the default board configuration for both GMAC instances.
    Init,
    /// Dump the current GMAC configuration and relevant GRF registers.
    Status,
    /// Update the RGMII clock divisor for the selected GMAC.
    Speed { id: GmacId, speed: GmacLinkSpeed },
    /// Override the RGMII TX/RX delays (raw GRF values).
    Delay { id: GmacId, delay: GmacDelay },
    /// Show usage information.
    Help,
}

/// Result returned after executing an Ethernet command.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Response {
    Init,
    Status(Status),
    Speed { id: GmacId, speed: GmacLinkSpeed },
    Delay { id: GmacId, delay: GmacDelay },
    Help,
}

/// Possible parsing failures for Ethernet commands.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParseError<'a> {
    MissingSubcommand,
    MissingArgument(&'static str),
    UnknownSubcommand(&'a str),
    InvalidGmac(&'a str),
    InvalidSpeed(&'a str),
    InvalidDelay(&'a str),
}

impl fmt::Display for ParseError<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseError::MissingSubcommand => write!(f, "missing subcommand"),
            ParseError::MissingArgument(arg) => write!(f, "missing {arg} argument"),
            ParseError::UnknownSubcommand(cmd) => write!(f, "unknown subcommand '{cmd}'"),
            ParseError::InvalidGmac(token) => write!(f, "invalid GMAC identifier '{token}'"),
            ParseError::InvalidSpeed(token) => {
                write!(f, "invalid speed '{token}' (expected 10/100/1000)")
            }
            ParseError::InvalidDelay(token) => {
                write!(f, "invalid delay '{token}' (use 0-0xff or 0x..)")
            }
        }
    }
}

/// Decoded GMAC status snapshot.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Status {
    /// Array containing status for GMAC0 and GMAC1 in order.
    pub macs: [GmacStatus; 2],
    /// Raw PHP_GRF_GMAC_CON0 register contents.
    pub phy_sel: u32,
    /// Raw PHP_GRF_CLK_CON1 register contents.
    pub clk_ctrl: u32,
    /// Raw SYS_GRF_GMAC_CON7 register contents.
    pub delay_enable: u32,
}

/// Per-GMAC status information.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GmacStatus {
    pub id: GmacId,
    pub interface: InterfaceMode,
    pub tx_delay: u8,
    pub rx_delay: u8,
    pub tx_delay_enabled: bool,
    pub rx_delay_enabled: bool,
    pub negotiated_speed: Option<GmacLinkSpeed>,
}

/// Supported PHY interface decoding result.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InterfaceMode {
    Rgmii,
    Rmii,
    Unknown,
}

/// Parse an Ethernet command string into a [`Command`].
pub fn parse_command<'a, I>(subcmd: &'a str, mut args: I) -> Result<Command, ParseError<'a>>
where
    I: Iterator<Item = &'a str>,
{
    match subcmd {
        "init" => Ok(Command::Init),
        "status" => Ok(Command::Status),
        "speed" => {
            let id_str = args.next().ok_or(ParseError::MissingArgument("gmac"))?;
            let speed_str = args.next().ok_or(ParseError::MissingArgument("speed"))?;
            let id = parse_gmac(id_str).ok_or(ParseError::InvalidGmac(id_str))?;
            let speed = parse_speed(speed_str).ok_or(ParseError::InvalidSpeed(speed_str))?;
            Ok(Command::Speed { id, speed })
        }
        "delay" => {
            let id_str = args.next().ok_or(ParseError::MissingArgument("gmac"))?;
            let tx_str = args.next().ok_or(ParseError::MissingArgument("tx"))?;
            let rx_str = args.next().ok_or(ParseError::MissingArgument("rx"))?;
            let id = parse_gmac(id_str).ok_or(ParseError::InvalidGmac(id_str))?;
            let tx = parse_delay(tx_str).ok_or(ParseError::InvalidDelay(tx_str))?;
            let rx = parse_delay(rx_str).ok_or(ParseError::InvalidDelay(rx_str))?;
            Ok(Command::Delay {
                id,
                delay: GmacDelay::new(tx, rx),
            })
        }
        "help" => Ok(Command::Help),
        other => Err(ParseError::UnknownSubcommand(other)),
    }
}

/// Execute a parsed Ethernet command.
pub fn execute(command: Command) -> Result<Response, ethernet_rk3588::Error> {
    let controller = ethernet_controller();
    match command {
        Command::Init => {
            controller.init();
            Ok(Response::Init)
        }
        Command::Status => Ok(Response::Status(snapshot(controller))),
        Command::Speed { id, speed } => {
            controller.set_link_speed(id, GmacPhyInterface::Rgmii, speed)?;
            Ok(Response::Speed { id, speed })
        }
        Command::Delay { id, delay } => {
            apply_delay(controller, id, delay);
            Ok(Response::Delay { id, delay })
        }
        Command::Help => Ok(Response::Help),
    }
}

fn parse_gmac(token: &str) -> Option<GmacId> {
    match token {
        "0" | "gmac0" | "GMAC0" => Some(GmacId::Gmac0),
        "1" | "gmac1" | "GMAC1" => Some(GmacId::Gmac1),
        _ => None,
    }
}

fn parse_speed(token: &str) -> Option<GmacLinkSpeed> {
    match token {
        "10" => Some(GmacLinkSpeed::Speed10),
        "100" => Some(GmacLinkSpeed::Speed100),
        "1000" => Some(GmacLinkSpeed::Speed1000),
        _ => None,
    }
}

fn parse_delay(token: &str) -> Option<u8> {
    if let Some(stripped) = token
        .strip_prefix("0x")
        .or_else(|| token.strip_prefix("0X"))
    {
        u8::from_str_radix(stripped, 16).ok()
    } else {
        token.parse().ok()
    }
}

fn snapshot(controller: &EthernetController) -> Status {
    let delay_enable = controller.read_sys_grf(SYS_GRF_GMAC_CON7);
    let delay_regs = [
        controller.read_sys_grf(SYS_GRF_GMAC_CON8),
        controller.read_sys_grf(SYS_GRF_GMAC_CON9),
    ];
    let phy_sel = controller.read_php_grf(PHP_GRF_GMAC_CON0);
    let clk_ctrl = controller.read_php_grf(PHP_GRF_CLK_CON1);

    let macs = [GmacId::Gmac0, GmacId::Gmac1].map(|id| {
        let idx = gmac_index(id);
        let delay = delay_regs[idx];
        let tx_delay = (delay & 0xff) as u8;
        let rx_delay = ((delay >> 8) & 0xff) as u8;
        let tx_delay_enabled = ((delay_enable >> (2 + idx * 2)) & 1) != 0;
        let rx_delay_enabled = ((delay_enable >> (3 + idx * 2)) & 1) != 0;

        let interface = decode_interface(phy_sel, id);
        let negotiated_speed = decode_speed(clk_ctrl, id, interface);

        GmacStatus {
            id,
            interface,
            tx_delay,
            rx_delay,
            tx_delay_enabled,
            rx_delay_enabled,
            negotiated_speed,
        }
    });

    Status {
        macs,
        phy_sel,
        clk_ctrl,
        delay_enable,
    }
}

fn apply_delay(controller: &EthernetController, id: GmacId, delay: GmacDelay) {
    // Reuse the existing configure_rgmii helper, which writes delay registers.
    let _ = controller.configure_rgmii(id, GmacPhyInterface::Rgmii, delay);
}

fn decode_interface(phy_sel: u32, id: GmacId) -> InterfaceMode {
    let base = 3 + gmac_index(id) as u32 * 6;
    let bit = |offset: u32| (phy_sel >> (base + offset)) & 1 != 0;

    match (bit(0), bit(1), bit(2)) {
        (true, false, false) => InterfaceMode::Rgmii,
        (false, false, true) => InterfaceMode::Rmii,
        _ => InterfaceMode::Unknown,
    }
}

fn decode_speed(clk_ctrl: u32, id: GmacId, interface: InterfaceMode) -> Option<GmacLinkSpeed> {
    let base = 5 * gmac_index(id) as u32;
    let bit = |offset: u32| (clk_ctrl >> (base + offset)) & 1 != 0;

    match interface {
        InterfaceMode::Rmii => {
            // RMII speed uses divider bits at base+2.
            if bit(2) {
                Some(GmacLinkSpeed::Speed100)
            } else {
                Some(GmacLinkSpeed::Speed10)
            }
        }
        InterfaceMode::Rgmii => match (bit(2), bit(3)) {
            (false, false) => Some(GmacLinkSpeed::Speed1000),
            (true, true) => Some(GmacLinkSpeed::Speed100),
            (false, true) => Some(GmacLinkSpeed::Speed10),
            _ => None,
        },
        InterfaceMode::Unknown => None,
    }
}

const fn gmac_index(id: GmacId) -> usize {
    match id {
        GmacId::Gmac0 => 0,
        GmacId::Gmac1 => 1,
    }
}
